package com.data.modules.system.util.crypto;

/**
 *
 * @author: cmg
 * @since: 2022-02-10 11:27
 **/

import com.alibaba.fastjson.JSONObject;
import com.data.modules.system.model.AccessTokenResult;
import com.data.modules.system.model.CommitApprovalApplyParam;
import com.google.common.base.CharMatcher;
import com.google.common.io.BaseEncoding;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.jeewx.api.mp.aes.AesException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

@SuppressWarnings("ALL")
@Slf4j
public class WxCryptUtil {
    private static final Base64 BASE64 = new Base64();
    private static final Charset CHARSET;
    private static final ThreadLocal<DocumentBuilder> BUILDER_LOCAL;
    protected byte[] aesKey;
    protected String token;
    protected String appidOrCorpid;

    public WxCryptUtil(String encodingAesKey, String appidOrCorpid) {
        this(null,encodingAesKey,appidOrCorpid);
    }

    public WxCryptUtil(String token, String encodingAesKey, String appidOrCorpid) {
        this.token = token;
        this.appidOrCorpid = appidOrCorpid;
        this.aesKey = BaseEncoding.base64().decode(CharMatcher.whitespace().removeFrom(encodingAesKey));
//        this.aesKey = Base64.decodeBase64(CharMatcher.whitespace().removeFrom(encodingAesKey));
    }

    private static String extractEncryptPart(String xml) {
        try {
            DocumentBuilder db = (DocumentBuilder)BUILDER_LOCAL.get();
            Document document = db.parse(new InputSource(new StringReader(xml)));
            Element root = document.getDocumentElement();
            return root.getElementsByTagName("Encrypt").item(0).getTextContent();
        } catch (Exception var4) {
            throw new RuntimeException(var4);
        }
    }

    private static byte[] number2BytesInNetworkOrder(int number) {
        byte[] orderBytes = new byte[]{(byte)(number >> 24 & 255), (byte)(number >> 16 & 255), (byte)(number >> 8 & 255), (byte)(number & 255)};
        return orderBytes;
    }

    private static int bytesNetworkOrder2Number(byte[] bytesInNetworkOrder) {
        int sourceNumber = 0;

        for(int i = 0; i < 4; ++i) {
            sourceNumber <<= 8;
            sourceNumber |= bytesInNetworkOrder[i] & 255;
        }

        return sourceNumber;
    }

    private static String genRandomStr() {
        String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        Random random = new Random();
        StringBuilder sb = new StringBuilder();

        for(int i = 0; i < 16; ++i) {
            int number = random.nextInt(base.length());
            sb.append(base.charAt(number));
        }

        return sb.toString();
    }

    private static String generateXml(String encrypt, String signature, String timestamp, String nonce) {
        String format = "<xml>\n<Encrypt><![CDATA[%1$s]]></Encrypt>\n<MsgSignature><![CDATA[%2$s]]></MsgSignature>\n<TimeStamp>%3$s</TimeStamp>\n<Nonce><![CDATA[%4$s]]></Nonce>\n</xml>";
        return String.format(format, encrypt, signature, timestamp, nonce);
    }

    public String encrypt(String plainText) {
        String encryptedXml = this.encrypt(genRandomStr(), plainText);
        String timeStamp = Long.toString(System.currentTimeMillis() / 1000L);
        String nonce = genRandomStr();
        String signature = SHA1.gen(new String[]{this.token, timeStamp, nonce, encryptedXml});
        return generateXml(encryptedXml, signature, timeStamp, nonce);
    }

    public String encrypt(String randomStr, String plainText) {
        ByteGroup byteCollector = new ByteGroup();
        byte[] randomStringBytes = randomStr.getBytes(CHARSET);
        byte[] plainTextBytes = plainText.getBytes(CHARSET);
        byte[] bytesOfSizeInNetworkOrder = number2BytesInNetworkOrder(plainTextBytes.length);
        byte[] appIdBytes = this.appidOrCorpid.getBytes(CHARSET);
        byteCollector.addBytes(randomStringBytes);
        byteCollector.addBytes(bytesOfSizeInNetworkOrder);
        byteCollector.addBytes(plainTextBytes);
        byteCollector.addBytes(appIdBytes);
        byte[] padBytes = PKCS7Encoder.encode(byteCollector.size());
        byteCollector.addBytes(padBytes);
        byte[] unencrypted = byteCollector.toBytes();

        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
            SecretKeySpec keySpec = new SecretKeySpec(this.aesKey, "AES");
            IvParameterSpec iv = new IvParameterSpec(this.aesKey, 0, 16);
            cipher.init(1, keySpec, iv);
            byte[] encrypted = cipher.doFinal(unencrypted);
            return BASE64.encodeToString(encrypted);
        } catch (Exception var14) {
            throw new RuntimeException(var14);
        }
    }

    public String decrypt(String msgSignature, String timeStamp, String nonce, String encryptedXml) {
        String cipherText = extractEncryptPart(encryptedXml);
        String signature = SHA1.gen(new String[]{this.token, timeStamp, nonce, cipherText});
        if (!signature.equals(msgSignature)) {
            throw new RuntimeException("加密消息签名校验失败");
        } else {
            return this.decrypt(cipherText);
        }
    }

    public String decrypt(String cipherText) {
        byte[] original;
        byte[] networkOrder;
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
            SecretKeySpec keySpec = new SecretKeySpec(this.aesKey, "AES");
            IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(this.aesKey, 0, 16));
            cipher.init(2, keySpec, iv);
            networkOrder = Base64.decodeBase64(cipherText);
            original = cipher.doFinal(networkOrder);
        } catch (Exception var9) {
            throw new RuntimeException(var9);
        }

        try {
            byte[] bytes = PKCS7Encoder.decode(original);
            networkOrder = Arrays.copyOfRange(bytes, 16, 20);
            int xmlLength = bytesNetworkOrder2Number(networkOrder);
            String xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET);
            new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), CHARSET);
            return xmlContent;
        } catch (Exception var8) {
            throw new RuntimeException(var8);
        }
    }


    /**
     * 验证URL
     * @param msgSignature 签名串，对应URL参数的msg_signature
     * @param timeStamp 时间戳，对应URL参数的timestamp
     * @param nonce 随机串，对应URL参数的nonce
     * @param echoStr 随机串，对应URL参数的echostr
     *
     * @return 解密之后的echostr
     * @throws RuntimeException 执行失败，请查看该异常的错误码和具体的错误信息
     */
    public String verifyURL(String msgSignature, String timeStamp, String nonce, String echoStr)
            throws RuntimeException {
        String signature = SHA1.getSHA1(token, timeStamp, nonce, echoStr);

        if (!signature.equals(msgSignature)) {
            throw new RuntimeException("解密失败");
        }

        String result = decrypt(echoStr);
        return result;
    }



    /**
     * 获取审批模板详情
     *
     * @param templateId id
     */
    public String getTemplateDetail(String templateId) {
        String url = "https://qyapi.weixin.qq.com/cgi-bin/oa/gettemplatedetail?access_token=ACCESS_TOKEN"
                .replace("ACCESS_TOKEN", token);

        RestTemplate restTemplate = new RestTemplate();
        Map<String, String> reqParam = new HashMap<>();
        reqParam.put("template_id", templateId);
        ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, reqParam, String.class);
        if (responseEntity.getStatusCode() == HttpStatus.OK) {
            return responseEntity.getBody();
        }
        return "失败";
    }


    /**
     * 提交审批申请
     *
     * @param param 参数
     * @return 编号
     */
    public String commitApprovalApply(String appAccessToken,CommitApprovalApplyParam param) throws Exception {
        String url = "https://qyapi.weixin.qq.com/cgi-bin/oa/applyevent?access_token=ACCESS_TOKEN"
                .replace("ACCESS_TOKEN", appAccessToken);

        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<JSONObject> responseEntity = restTemplate.postForEntity(url, param, JSONObject.class);
        if (responseEntity.getStatusCode() == HttpStatus.OK) {
            JSONObject body = responseEntity.getBody();
            log.info("提交审批流后返回结果{}",body.toJSONString());
            if (body.getInteger("errcode") == 0) {
                return body.getString("sp_no");
            }else{
                log.error(body.toJSONString());
                throw new Exception(body.toJSONString());
            }
        }
        throw new Exception("提交审批流程失败");
    }



    static {
        CHARSET = StandardCharsets.UTF_8;
        BUILDER_LOCAL = ThreadLocal.withInitial(() -> {
            try {
                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                factory.setExpandEntityReferences(false);
                factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
                return factory.newDocumentBuilder();
            } catch (ParserConfigurationException var1) {
                throw new IllegalArgumentException(var1);
            }
        });
    }
}

